Replace COA.call with COA.callWithSigAndArgs for reduced computation cost#77
Replace COA.call with COA.callWithSigAndArgs for reduced computation cost#77
COA.call with COA.callWithSigAndArgs for reduced computation cost#77Conversation
ae6fa70 to
12290b2
Compare
12290b2 to
85dc5f3
Compare
|
|
||
| if result.status != EVM.Status.successful { | ||
| let errorMsg = FlowYieldVaultsEVM.decodeEVMError(result.data) | ||
| let errorMsg = FlowYieldVaultsEVM.decodeEVMError(result.results as! [UInt8]) |
There was a problem hiding this comment.
Unverified type assumption in all 13 error paths
Every failure branch in this PR casts result.results as! [UInt8] (13 sites across the contract). The old code used result.data, which was a dedicated [UInt8] field guaranteed to hold raw revert bytes. The new callWithSigAndArgs/dryCallWithSigAndArgs API stores results in [AnyStruct] — a field shared by both success (decoded Cadence values) and failure (raw revert bytes).
The downcast result.results as! [UInt8] succeeds only if the dynamic type of results on failure is [UInt8] at the array level. If the implementation wraps the bytes as a single element (e.g. [[UInt8]]) or populates results element-by-element from [AnyStruct], the downcast panics at runtime with a Cadence type error instead of decoding and propagating the actual EVM error.
CI is explicitly broken because callWithSigAndArgs isn't deployed yet, so this assumption is currently untested. Before merging, please add at least one Cadence test that exercises a failing EVM call via the new API and confirms that result.results as! [UInt8] correctly contains the revert data.
| let vaultIdentifiers = decoded[9] as! [String] | ||
| let strategyIdentifiers = decoded[10] as! [String] | ||
| if callResult.status != EVM.Status.successful { | ||
| let errorMsg = FlowYieldVaultsEVM.decodeEVMError(callResult.results as! [UInt8]) |
There was a problem hiding this comment.
Graceful nil return replaced with potential panic in critical scheduler path
In the old code decodeEVMError(callResult.data) was safe — data was always [UInt8]. Here callResult.results as! [UInt8] may panic if the API's failure representation isn't a bare [UInt8] (see discussion on line 994).
The consequence here is worse than in state-mutating paths: getPendingRequestsFromEVM is called by the SchedulerHandler to decide which requests to process. Old behaviour on EVM call failure: emit ErrorEncountered, return nil, SchedulerHandler skips gracefully. New behaviour if the cast panics: the entire SchedulerHandler transaction reverts, crash recovery must handle it, and the root cause (EVM call failure + reason) is lost.
Additionally, resultTypes here includes Type<[UInt8]>() (for requestTypes and statuses). If the EVM runtime tries to decode even on failure, results could be partially or incorrectly populated — making the as! [UInt8] cast semantically ambiguous on the failure path. Consider using a dedicated helper that checks callResult.status first and accesses raw error bytes via a well-typed accessor rather than a force-cast on the shared results field.
Review: Replace
|
Related: onflow/flow-go#8401
Closes: #76
Description
Previously, to perform contract calls using a COA, it was necessary to produce the
callDataon Cadence side:To be able to ABI-decode the returned data from the COA call, another step was needed:
After doing some profiling on Cadence transactions, the computation cost of ABI encoding/decoding, amounted to about 15%-20% of the total computation, which is not trivial.
To optimize this case, we have a new wrapper function,
callWithSigAndArgs, which does all this more efficiently:We do the same for
dryCall, by replacing it withdryCallWithSigAndArgs.NOTE: CI is failing because we still need to deploy the
EVMsystem contract on testnet/mainnet, for these new functions to be available.